package fi.saarinen.loader.elf;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

/**
 * Loader for the ELF header.
 * 
 * @author jsaarinen
 */
public class Header implements Loader
{
  private final static int EI_NIDENT = 16;
  private byte[] eIdent;

  private short eType;
  private short eMachine;
  private int eVersion;
  private int eEntry;
  private int ePhoff;
  private int eShoff;
  private int eFlags;
  private int eEhsize;
  private short ePhentsize;
  private short ePhnum;
  private short eShentsize;
  private short eShnum;
  private short eShstrndx;

  private static final int ELFCLASSNONE = 0;
  private static final int ELFCLASS32 = 1;
  private static final int ELFCLASS64 = 2;
  
  private static final int ELFDATANONE = 0;
  private static final int ELFDATA2LSB = 1;
  private static final int ELFDATA2MSB = 2;
    
  private static final int ET_NONE   = 0;
  private static final int ET_REL    = 1;
  private static final int ET_EXEC   = 2;
  private static final int ET_DYN    = 3;
  private static final int ET_CORE   = 4;
  private static final int ET_LOPROC = 0xff00; 
  private static final int ET_HIPROC = 0xffff;
  
  private static final int EM_NONE        = 0;
  private static final int EM_M32         = 1;
  private static final int EM_SPARC       = 2;
  private static final int EM_386         = 3;
  private static final int EM_68K         = 4;
  private static final int EM_88K         = 5;
  private static final int EM_860         = 7;
  private static final int EM_MIPS        = 8;
  private static final int EM_MIPS_RS4_BE = 10;
  private static final int RESERVED0      = 11;
  private static final int RESERVED1      = 12;
  private static final int RESERVED2      = 13;
  private static final int RESERVED3      = 14;
  private static final int RESERVED4      = 15;
  private static final int RESERVED5      = 16;
    
  public Header()
  {
    this.eIdent = new byte[EI_NIDENT];
  }

  public int load(final byte[] mem, int offset) throws ELFException
  {
    offset = parseIdent(mem, offset);
    offset += parseRest(mem, offset);
    return offset;
  }

  private int parseRest(final byte[] mem, int offset) throws ELFException
  {
    this.eType = IO.readHalf(mem, offset);
    offset += 2;
    
    if (this.eType != ET_REL)
    {
      throw new ELFException("Invalid object file type: " + Integer.toHexString(this.eType));
    }
    
    this.eMachine = IO.readHalf(mem, offset);
    offset += 2;
    if (this.eMachine != EM_68K)
    {
      throw new ELFException("Invalid machine file type: " + Integer.toHexString(this.eMachine));
    }   
    
    this.eVersion = IO.readWord(mem, offset);
    offset += 4;
    
    this.eEntry = IO.readAddr(mem, offset);
    offset += 4;

    this.ePhoff = IO.readOff(mem, offset);
    offset += 4;

    this.eShoff = IO.readOff(mem, offset);
    offset += 4;
    
    this.eFlags = IO.readWord(mem, offset);
    offset += 4;

    this.eEhsize = IO.readHalf(mem, offset);
    offset += 2;

    this.ePhentsize = IO.readHalf(mem, offset);
    offset += 2;

    this.ePhnum = IO.readHalf(mem, offset);
    offset += 2;

    this.eShentsize = IO.readHalf(mem, offset);
    offset += 2;

    this.eShnum = IO.readHalf(mem, offset);
    offset += 2;

    this.eShstrndx = IO.readHalf(mem, offset);
    offset += 2;

    return offset;
  }

  private int parseIdent(byte[] mem, int offset) throws ELFException
  {
    int mag0 = IO.readUChar(mem, offset);
    offset++;
    
    int mag1 = IO.readUChar(mem, offset);
    offset++;

    int mag2 = IO.readUChar(mem, offset);
    offset++;

    int mag3 = IO.readUChar(mem, offset);
    offset++;

    if (mag0 != 0x7F ||
        mag1 != 'E' ||
        mag2 != 'L' ||
        mag3 != 'F')
    {
      throw new ELFException("Not a ELF file!");
    }

    int clazz = IO.readUChar(mem, offset);
    offset++;
    if (clazz != ELFCLASS32)
    {
      throw new ELFException("Invalid EI_CLASS");
    }

    int encoding = IO.readUChar(mem, offset);
    offset++;
    if (encoding != ELFDATA2MSB)
    {
      throw new ELFException("Invalid EI_DATA, only big-endian supported!");
    }
    
    int version = IO.readUChar(mem, offset);
    offset++;
    
    // read padding
    for (int i = 0; i < 9; i++)
    {
      int pad = IO.readUChar(mem, offset);
      offset++;
    }
    
    return offset;
  }  
    
  /**
   * @return the eIdent
   */
  public byte[] geteIdent()
  {
    return this.eIdent;
  }

  /**
   * @return the eType
   */
  public short geteType()
  {
    return this.eType;
  }

  /**
   * @return the eMachine
   */
  public short geteMachine()
  {
    return this.eMachine;
  }

  /**
   * @return the eVersion
   */
  public int geteVersion()
  {
    return this.eVersion;
  }

  /**
   * @return the eEntry
   */
  public int geteEntry()
  {
    return this.eEntry;
  }

  /**
   * @return the ePhoff
   */
  public int getePhoff()
  {
    return this.ePhoff;
  }

  /**
   * @return the eShoff
   */
  public int geteShoff()
  {
    return this.eShoff;
  }

  /**
   * @return the eFlags
   */
  public int geteFlags()
  {
    return this.eFlags;
  }

  /**
   * @return the eEhsize
   */
  public int geteEhsize()
  {
    return this.eEhsize;
  }

  /**
   * @return the ePhentsize
   */
  public short getePhentsize()
  {
    return this.ePhentsize;
  }

  /**
   * @return the ePhnum
   */
  public short getePhnum()
  {
    return this.ePhnum;
  }

  /**
   * @return the eShentsize
   */
  public short geteShentsize()
  {
    return this.eShentsize;
  }

  /**
   * @return the eShnum
   */
  public short geteShnum()
  {
    return this.eShnum;
  }

  /**
   * @return the eShstrndx
   */
  public short geteShstrndx()
  {
    return this.eShstrndx;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString()
  {
    return "Header [eIdent=" + Arrays.toString(this.eIdent) + ", eType="
        + this.eType + ", eMachine=" + this.eMachine + ", eVersion="
        + this.eVersion + ", eEntry=" + this.eEntry + ", ePhoff=" + this.ePhoff
        + ", eShoff=" + this.eShoff + ", eFlags=" + this.eFlags + ", eEhsize="
        + this.eEhsize + ", ePhentsize=" + this.ePhentsize + ", ePhnum="
        + this.ePhnum + ", eShentsize=" + this.eShentsize + ", eShnum="
        + this.eShnum + ", eShstrndx=" + this.eShstrndx + "]";
  }

  public static void main(String[] args) throws Exception
  {
    Header header = new Header();
    byte[] mem = IO.mmap("/home/guest/t.o");
    header.load(mem, 0);
    System.out.println(header.toString());
  }
}
